
/* 
 *	VoteTally.m	Written by Joe Freeman 5/27/92
 *
 * Note: 
 *	You can talk back to a remote object, if it gives you
 * 	its id.  But, you cannot, while responding to a message that came 
 * 	remote, send a message to a remote server in the same process.  That  
 * 	be blocked out by the conversation that you are responding to.
 *
 *	You can talk to the NXConnection object in the client, that
 *	was created when connecting to the server, if the client has
 *	done a runFromAppkit using that connection object
 *
 * Also,
 *	clientList = nil if we are not the server
 *
 */
#import "VoteTally.h"

@implementation VoteTally

#define numAttendees	([clientList count] )
- appDidInit:sender
{

    /* You should really give it the name of the machine you want it to
     * connect to. The "*" will cause the server to look at every machine
     * on the subnet.  That could take a LONG time.
     */
    remoteHub = [NXConnection connectToName:"VoteTally" onHost:"*"];
    if (remoteHub){
    	[clearAllMenu setEnabled:NO];
	
	/* if someone is already the hub, then we will be the client
 	 * we find the NXConnection object that is handling the Proxy
	 * that represents the vote server.  We then register this object
	 * to handle errors for that server 
	 *
	 * the client understands more than this protocol 
	 * but these are used most so lets tell it about the them.
	 */
	[[scrollers window] setTitle: "Voting Client"];
        ourServer = [remoteHub connectionForProxy];
	[remoteHub setProtocolForProxy:@protocol(VoteTallyServerMethods)];

    } else {
	/* The fact that no one is registered, means that we are going to
	 * be the server.  So, we register our connection 
	 * 
	 * The server understands the client protocol also 
	 * but these are the most common messages
	 */
	clientList = [[List alloc] init];
	[[scrollers window] setTitle: "Voting Server"];
	ourServer = [NXConnection registerRoot: self withName:"VoteTally"];
	
	/* add ourselves as a psuedo client */
	remoteHub = self;
    }

    /* 
     *	we take ourServer, which represets the NXConnection objec that
     *  will handle all our proxys (?) and make ourselves the handler for
     *  errors for that object.  Then we run from appkit.
     *
     *	The case for the voting server makes sense.  We need to run from
     *	appkit so that we can server all the clients.  
     *
     *	The case for the clients is not as clear.   We register the 
     *	NXConnection that is behind our connection to the vote server
     *	so that the server, which will have our ID will be able to talk
     *	back without the client having initiated the connection.  This is
     *	NOT OBVIOUS from the PR1 docs
     */
    [ourServer registerForInvalidationNotification:self];
    [ourServer runFromAppKit];
    [remoteHub addClient:self];

    return self;
}

- appWillTerminate:sender
{
    [remoteHub removeClient:self];
    return self;
}

- free
{
    [super free];
    [ourServer free];
    return self;
}
 
/* ============================================================
 *	who is part of this conference anyway?
 *
 *	All three of these messages may cause traffic to ALL the
 * 	clients which means the NXConnection objects on those 
 *	need to be free.  So, we declare these oneway to release
 *
 * ============================================================ */

- addClient:sender
{
    id connectionToClient;
    
    if (sender != self){
    	/* do some error trapping and optimizaitons */
	
    	/* figure out who is behind this new client and get their errors */
    	connectionToClient = [sender connectionForProxy];
    	[connectionToClient registerForInvalidationNotification:self];
      	  
    	/* make sure this new object is smart about the client messages */
    	[sender setProtocolForProxy:@protocol(VoteTallyClientMethods)];
    }
    
    /* do some bookeeping */
    [clientList addObjectIfAbsent:sender];
    [self updateStats];
    return self;
}

- removeClient:anObject
{
    [clientList removeObject:anObject];
    if ( anObject != self ){
    	/* the server should rebuild the stats */
	[self updateStats];
    }
    return self;
}

/* an updateStats:message means two different things, depending on server
 * Server	rebuilds the stats and then tells all clients to get stats
 * Client	gets all the stats from the server
 */
#define NUMSLIDERS 6

-  (void) updateStats
{
    int last = numAttendees;
    int i,s;
    float sliders[NUMSLIDERS];
    float tmp;
    int   iTmp;

///    fprintf(stderr,"%d updateStats with remote hub = %d\n", self, remoteHub);

    [remoteHub getNumAttend:&i];
    [numVoting setIntValue: i];
    
    if (remoteHub != self){
	for ( s = 0 ; s < NUMSLIDERS; s++){
		[remoteHub getMean:&tmp at:s];
		[[means findCellWithTag:s] setFloatValue:tmp];
	}
	
    } else {
    	/* build new stats */
	for ( s = 0 ; s < NUMSLIDERS; s++){
		sliders[s] = 0.0;
		for ( i = 0 ; i < last; i++){
			[[clientList objectAt:i] getValue:&iTmp at:s];
			sliders[s]+= iTmp;
		}
		sliders[s] = sliders[s]/numAttendees;
		[[means findCellWithTag:s] setFloatValue:sliders[s]];
	}

///	fprintf(stderr,"%d stats rebuilt \n", self);
	/* tell all the clients to update their displays from us */
	for ( i = 0 ; i < last ; i++){
		if ([clientList objectAt:i] != self)
			[[clientList objectAt:i] updateStats];
 	}
    }
}
 

/* ============================================================
 *
 * ============================================================ */

/* 
 * only the hub is allowed to restore everyone's score
 */
- resetAll:sender
{
    int last = numAttendees;
    [clientList makeObjectsPerform:@selector(resetLocal:) with:self];
    [self updateStats];
    return self;
}

/* reset is the same as moving all sliders.  So, we should notify hub 
 * if the hub sent us this message, then it already knows 
 */
- resetLocal:sender
{
    int i,numRows,numCols;
    [scrollers getNumRows:&numRows numCols:&numCols];
    for ( i = 0 ; i < numCols; i++){
	[[scrollers findCellWithTag:i] setIntValue:50]; 
    }
    if (sender != remoteHub)
    	[remoteHub updateStats];
    return self;
}

/* a slider has moved... have the hub query everyone for new stats */
- takeVoteFrom:sender
{
   [remoteHub updateStats];
   return self;
}

/* ============================================================
 *
 * ============================================================ */

- getNumAttend:(int *)anInt
{
    *anInt = numAttendees;
    return self;
}

- getValue:(int *)aVal at:(int)index
{
    *aVal = [[scrollers findCellWithTag:index] intValue];
    return self;
}

- getMean:(float *)aFloat at:(int)index
{
    *aFloat = [[means findCellWithTag:index] floatValue];
    return self;
}

/* ============================================================
 *
 *	part of the registerForInvalidationNotification stuff
 *
 *
 * ============================================================ */

- senderIsInvalid:sender
{
    if (self != remoteHub){
    	/* server is dead, so lets kill ourselves */
	remoteHub = nil;
	[NXApp terminate:self];
    } else {
    	/* the server ran into a dead client */
	[self removeClient:sender];
    }
    return self;
}

@end
